﻿using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using eWallet.Serialization;

namespace eWallet.Security.Cryptography
{
	/// <summary>
	/// A base implementation of the <see cref="ICryptoModule"/> interface.
	/// </summary>
	public abstract class CryptoModule : ICryptoModule
	{

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		protected CryptoModule() { }



		/// <summary>
		/// Returns a converter that can convert from a string to a byte array.
		/// </summary>
		public static Converter<string, byte[]> StringInputConverter
		{
		    get
		    {
				return delegate(string s)
				{
					return Encoding.UTF8.GetBytes(s);
				};
		    }
		}

		/// <summary>
		/// Returns a converter that can convert from a byte array to a string.
		/// </summary>
		public static Converter<byte[], string> StringOutputConverter
		{
			get
			{
				return delegate(byte[] buffer)
				{
					return Encoding.UTF8.GetString(buffer);
				};
			}
		}

		/// <summary>
		/// Returns a converter that can convert from a symmetric algorithm to a byte array.
		/// </summary>
		public static Converter<SymmetricAlgorithm, byte[]> SymmetricAlgorithmInputConverter
		{
			get
			{
				return delegate(SymmetricAlgorithm algorithm)
				{
					return BinarySerializer.Serialize(new SymmetricAlgorithmInfo(algorithm));
				};
			}
		}

		/// <summary>
		/// Returns a converter that can convert from a byte array to a symmetric algorithm.
		/// </summary>
		public static Converter<byte[], SymmetricAlgorithm> SymmetricAlgorithmOutputConverter
		{
			get
			{
				return delegate(byte[] buffer)
				{
					return BinarySerializer.Deserialize<SymmetricAlgorithmInfo>(buffer).CreateAlgorithm();
				};
			}
		}



		/// <summary>
		/// Returns the encryption method supported by the crypto module.
		/// </summary>
		public abstract EncryptionMethod EncryptionMethod { get; }



		/// <summary>
		/// When implemented in a class, decrypts the given cipher data.
		/// </summary>
		/// <param name="cipher">The encrypted data to decrypt.</param>
		/// <remarks>
		/// This is an abstract method that must be implemented in a derived class.
		/// </remarks>
		public abstract byte[] Decrypt(byte[] cipher);

		/// <summary>
		/// Decrypts the given cipher data and converts the decrypted data to the type
		/// specified in <typeparamref name="TOutput"/> by using the converter
		/// specified in <paramref name="outputConverter"/>.
		/// </summary>
		/// <typeparam name="TOutput">The type to return the decrypted data as.</typeparam>
		/// <param name="cipher">The encrypted data.</param>
		/// <param name="outputConverter">A converter that can convert from a byte array to the type specified in <typeparamref name="TOutput">.</param>
		/// <remarks>
		/// <para>
		/// You can use the converters defined as static properties on this class for the
		/// <paramref name="outputConverter"/> converter.
		/// </para>
		/// <para>
		/// This method can be overridden in a derived class.
		/// </para>
		/// </remarks>
		public virtual TOutput Decrypt<TOutput>(byte[] cipher, Converter<byte[], TOutput> outputConverter)
		{
			return outputConverter.Invoke(this.Decrypt(cipher));
		}

		/// <summary>
		/// Decrypts the data stored in the stream specified in <paramref name="cipherStream"/>
		/// and writes the decrypted data to the stream specified in <paramref name="dataStream"/>.
		/// </summary>
		/// <param name="cipherStream">The stream containin the encrypted data.</param>
		/// <param name="dataStream">The stream where to write the decrypted data to.</param>
		/// <remarks>
		/// This method can be overridden in a derived class.
		/// </remarks>
		public virtual void Decrypt(Stream cipherStream, Stream dataStream)
		{
			long l = cipherStream.Length - cipherStream.Position;
			byte[] cipher = new byte[l];
			cipherStream.Read(cipher, 0, cipher.Length);
			byte[] data = this.Decrypt(cipher);
			dataStream.Write(data, 0, data.Length);
		}

		/// <summary>
		/// Encrypts the given data.
		/// </summary>
		/// <param name="data">The data to encrypt.</param>
		/// <remarks>
		/// This is an abstract method that must be implemented in a derived class.
		/// </remarks>
		public abstract byte[] Encrypt(byte[] data);
		
		/// <summary>
		/// Encrypts the given data by first converting it to a byte array using the converter
		/// specified in <paramref name="inputConverter"/>.
		/// </summary>
		/// <typeparam name="TInput">The type of the data to encrypt.</typeparam>
		/// <param name="inputData">The data to encrypt.</param>
		/// <param name="inputConverter">A converter that converts from the type specified in <typeparamref name="TInput"/> to a byte array.</param>
		/// <remarks>
		/// <para>
		/// You can use the converters defined as static properties on this class for the
		/// <paramref name="outputConverter"/> converter.
		/// </para>
		/// <para>
		/// This method can be overridden in a derived class.
		/// </para>
		/// </remarks>
		public virtual byte[] Encrypt<TInput>(TInput inputData, Converter<TInput, byte[]> inputConverter)
		{
			return this.Encrypt(inputConverter.Invoke(inputData));
		}

		/// <summary>
		/// Encrypts the data from the stream specified in <paramref name="dataStream"/> and writes
		/// the encrypted data to the stream specified in <paramref name="cipherStream"/>.
		/// </summary>
		/// <param name="dataStream">The stream containing the data to encrypt.</param>
		/// <param name="cipherStream">The stream to write the encrypted data to.</param>
		/// <remarks>
		/// This method can be overridden in a derived class.
		/// </remarks>
		public virtual void Encrypt(Stream dataStream, Stream cipherStream)
		{
			long l = dataStream.Length - dataStream.Position;
			byte[] data = new byte[l];
			dataStream.Read(data, 0, data.Length);
			byte[] cipher = this.Encrypt(data);
			cipherStream.Write(cipher, 0, cipher.Length);
		}

		/// <summary>
		/// When implemented in a class, returns a description that represents the data that is
		/// used to perform encryption and decryption in the crypto module, for instance a
		/// hash of that data.
		/// </summary>
		/// <remarks>
		/// This method must not return a string that can be used to reproduce the data that
		/// is used to encrypt and decrypt data with.
		/// </remarks>
		public virtual string GetDescription()
		{
			return this.EncryptionMethod.ToString();
		}

	}
}
